#!/usr/bin/env python
# coding=utf-8
##########################################################################
# AUTHOR   : DC-Melo
# MAIL     : melo.da.chor@gmail.com
# BLOG     : www.dc-melo.com
# FILE     : cantest.py
# CREATED  : 2020-11-10 15:50
# MODIFIED : 2020-11-27 11:31
# VERSION  : V-0.0.2.201127_a:add opt parameter function;V-0.0.1.201110_a: ;
# DESCRIB  : 
# NOTICES  : 
##########################################################################
# from __future__ import print_function
import os
import sys
import time
import platform
import re
import getopt
import subprocess
import threading # 导入线程包
import pymysql
import select
import can
import signal
import cantools
import pandas as pd
from pprint import pprint
import serial  #导入pyserial包
import serial.tools.list_ports
# from serial.tools import list_ports
from binascii import *
from crcmod import *
import chardet
from ppadb.client import Client as AdbClient
if (platform.system() != 'Linux'):
    import pyttsx3
    import pyttsx3.drivers
    import pyttsx3.drivers.sapi5
    import pythoncom
 
msg_send_dict = dict()
bus_msg_recv_dict = dict()
global is_exit
is_exit = False
lock_print=True
lk=threading.Lock()
conf_dict =dict()
conf_dict['RS232SerialPort']   = ''
conf_dict['CANChanel']         = ''
conf_dict['DeviceSerialNo']    = ''
conf_dict['DBCFile']           = ''
conf_dict['CaseScript']        = ''
conf_dict['host']              = ''
conf_dict['port']              = ''
conf_dict['user']              = ''
conf_dict['password']          = ''
conf_dict['CaseDatabase']      = ''
conf_dict['CaseTable']         = ''
conf_dict['CaseProjectColumn'] = ''
conf_dict['PrintInfo'] = ''
global_var = "adfas"
# CRC16-MODBUS
def crc16Add(read):
    crc16 =crcmod.mkCrcFun(0x18005,rev=True,initCrc=0xFFFF,xorOut=0x0000)
    data = read.replace(" ","")
    readcrcout=hex(crc16(unhexlify(data))).upper()
    str_list = list(readcrcout)
    if len(str_list) == 5:
        str_list.insert(2,'0')      # 位数不足补0
    crc_data = "".join(str_list)
    # print(crc_data)
    read = read.strip()+' '+crc_data[4:]+' '+crc_data[2:4]
    # print('CRC16校验:',crc_data[4:]+' '+crc_data[2:4])
    # print('增加Modbus CRC16校验：>>>',read)
    return read
 
def data_out_sql(DB, sql):
    conn = pymysql.connect(host=DB['host'], port=DB['port'], user=DB['user'], passwd=DB['password'], db=DB['CaseDatabase'], charset='utf8')
    # 创建游标
    cursor = conn.cursor()
    # 执行sql语句
    cursor.execute(sql)
    # 调出数据
    data = cursor.fetchall()
    # cols为字段信息 例如(('factory_id', 253, None, 6, 6, 0, False), ('szDeviceId', 253, None, 30, 30, 0, False),('update_time', 7, None, 19, 19, 0, False))
    cols = cursor.description
    # 执行
    conn.commit()
    conn.close()
    # 将数据truple转换为DataFrame
    col = []
    for i in cols:
        col.append(i[0])
    data = list(map(list, data))
    data = pd.DataFrame(data,columns=col)
    print(data)
    return data

def interpriter(client,db,cmd_list):
    global is_exit
    lk.acquire()
    msg_send_dict.clear()
    lk.release()
    loop_curr = 0
    step=0
    while step < len(cmd_list):
        if is_exit:break
        each_line=cmd_list[step].strip()
        step += 1
        each_line_cmd = each_line.split("#", 1)[0].strip()
        # 去掉备注部分或备注行
        if not each_line_cmd:
            print(each_line)
            continue
        # 查找end
        item_l=re.findall(r"(\d+\.?\d*)\s+loop:([\da-zA-Z_]+)\s*=(.*)", each_line_cmd)
        if item_l:
            loop_begin=step
            loop_curr=1
            loop_count=int(item_l[0][2])
            loop_var=item_l[0][1]
            print(each_line)
            continue
        item_l=re.findall(r"(\d+\.?\d*)\s+loop:end", each_line_cmd)
        if item_l:
            if loop_curr<=loop_count:
                step=loop_begin
                print(each_line,loop_var," = ",loop_curr,"/",loop_count)
                loop_curr += 1
            else:
                loop_curr = 0
            continue
        item_l=re.findall(r"(\d+\.?\d*)\s+([\da-zA-Z_]+):([\da-zA-Z_]+)\s*=(.*)", each_line_cmd)
        if not item_l:
            print(each_line)
            continue
        item_l=item_l[0]
        if len(item_l) < 4:
            print(each_line," Error command format")
            continue
        time.sleep(float(item_l[0]))
        if item_l[1].upper() in ("RS232","HARDWARE"):
            if item_l[2].upper() in ("RELAY1","GND"):
                relay_add="00 00 "
                relay_act="FF 00 " if float(item_l[3])==0 else "00 00 "   
            if item_l[2].upper() in ("RELAY2", "POWER"):
                relay_add="00 01 "
                relay_act="FF 00 " if float(item_l[3])==0 else "00 00 "   
            if item_l[2].upper() in ("RELAY3", "ACC"):
                relay_add="00 02 "
                relay_act="FF 00 " if float(item_l[3])==0 else "00 00 "   
            if item_l[2].upper() in ("RELAY4", "REVERSE"):
                relay_add="00 03 "
                relay_act="FF 00 " if float(item_l[3])!=0 else "00 00 "   
            if item_l[2].upper() in ("RELAY5", "PARK"):
                relay_add="00 04 "
                relay_act="FF 00 " if float(item_l[3])!=0 else "00 00 "   
            if item_l[2].upper() in ("RELAY6", "RESERVE"):
                relay_add="00 05 "
                relay_act="FF 00 " if float(item_l[3])!=0 else "00 00 "   
            if item_l[2].upper() in ("RELAY7", "RESERVE"):
                relay_add="00 06 "
                relay_act="FF 00 " if float(item_l[3])!=0 else "00 00 "   
            if item_l[2].upper() in ("RELAY8", "RESERVE"):
                relay_add="00 07 "
                relay_act="FF 00 " if float(item_l[3])!=0 else "00 00 "   
            command=crc16Add("01 05 "+relay_add+relay_act)
            try:
                ser1=serial.Serial(conf_dict['RS232SerialPort'],9600,timeout=0.5)
                result=ser1.write(bytes.fromhex(command))#写数据
                ser1.close()
                print(each_line,"send relay:",command)
            except Exception as e:
                print("串口错误：",e)
        elif item_l[1].upper() == "ADB":
            print(each_line)
            devices = client.devices()
            if item_l[2].upper() in ("CHECK",):
                timeout = False
                while not devices and not timeout:
                    print("未检测到adb,等待%s后检查"%item_l[3])
                    if int(item_l[3]):
                        time.sleep(int(item_l[3]))
                        timeout=True
                    else:
                        time.sleep(10)
                    devices = client.devices()
            if not devices:
                print("请连接usb到电脑，并打开ADB设置!(等待10s)")
                time.sleep(10)
                devices = client.devices()
            if item_l[2].upper() in ("SHELL",):
                try:
                    shellret = devices[0].shell(item_l[3])
                    print("adb shell ",item_l[3],"返回：",shellret)
                except Exception as e:
                    print("adb连接错误：",e)
            elif item_l[2].upper() in ("PULL",):
                paras=item_l[3].split()
                try:
                    pullret=device[0].pull(paras[0], paras[1])
                    print("adb pull ",item_l[3],"返回：",pullret)
                except Exception as e:
                    print("adb连接错误：",e)
            elif item_l[2].upper() in ("PUSH",):
                paras=item_l[3].split()
                try:
                    pushret=device[0].push(paras[0], paras[1])
                    print("adb push ",item_l[3],"返回：",pushret)
                except Exception as e:
                    print("adb连接错误：",e)
            elif item_l[2].upper() in ("SCREENSHOT","SCREENCAP"):
                ts=time.strftime("%Y%m%d-%H%M%S", time.localtime())
                try:
                    result = device[0].screencap()
                    with open(item_l[3]+ts, "wb") as fp:
                        fp.write(result)
                except Exception as e:
                    print("adb连接错误：",e)
            elif item_l[2].upper() in ("LOGCAT",):
                ts=time.strftime("%Y%m%d-%H%M%S", time.localtime())
                if   "-d" in item_l[3]:
                    os.system('adb logcat -d > logcat-d'+ts+'.log')
                elif "-c" in item_l[3]:
                    os.system('adb logcat -c ')
                else:
                    print("logcat 只支持-c 或-d参数")
        elif item_l[1].upper() in ("SPEECH","VOICE"):
            if item_l[2].upper() in ("SAY",):
                pythoncom.CoInitialize()
                engine = pyttsx3.init()
                engine.say(item_l[3])
                engine.runAndWait()
                pythoncom.CoUninitialize
        else:
            frame_id_l=re.findall(r"(_|0x)(\S*)",item_l[1])
            if not frame_id_l:
                print("error format",frame_id_l)
                continue
            frame_id=int(frame_id_l[0][1],16)
            lk.acquire()
            try:
                if frame_id not in msg_send_dict:
                    msg_send_dict[frame_id] = db.get_message_by_frame_id(frame_id)
                    # msg_send_dict[frame_id].signals_value={sig.name:sig.initial for sig in msg_send_dict[frame_id].signals}
                    msg_send_dict[frame_id].signals_value={sig.name:sig.minimum for sig in msg_send_dict[frame_id].signals}
                    msg_send_dict[frame_id].timestamp=0
                msg_send_dict[frame_id].signals_value[item_l[2]]=eval(item_l[3].replace(loop_var,str(loop_curr))) if loop_curr else eval(item_l[3])
            except Exception as e:
                print("set signal error:",e)
            if 'y' in conf_dict['PrintInfo'].lower():
                sig=next(filter(lambda x: x.name == item_l[2],  msg_send_dict[frame_id]._signals))
                print(each_line,sig.choices)
            else:
                print(each_line)               
            # print(sig.choices)
            lk.release()

def read_thread(client):
    global msg_send_dict
    global lock_print
    global conf_dict
    global is_exit
    db = cantools.database.load_file(filename=conf_dict['DBCFile'], database_format="dbc", encoding=None, frame_id_mask=None, strict=None, cache_dir=None)
    if os.path.exists(conf_dict['CaseScript']):
        # 首先二进制方式打开文件
        with open(conf_dict['CaseScript'], 'rb') as frb:
            # 检测编码方式
            cur_encoding = chardet.detect(frb.read())['encoding']
        # 指定文件编码方式
        with open(conf_dict['CaseScript'], 'r', encoding=cur_encoding) as fr:
            cmd_list = fr.readlines()
        interpriter(client,db,cmd_list)
    else:
        sql = 'select * from %s;' % conf_dict['CaseTable'] #需要写入excel表数据
        df=data_out_sql(conf_dict,sql)
        for index, row in df.iterrows():
            # cmd_list=row["S202DA"].strip()
            if is_exit: break
            cmd_list=row[conf_dict['CaseProjectColumn']].strip()
            if len(cmd_list)==0:
                print("用例编号:%s. %s"%(row["id"],row["name"].replace('\r', '').replace('\n', '')))
                continue
            print("用例编号:%s"%row["name"].replace('\r', '').replace('\n', ''))
            print("用例目录:%s"%row["catalogue"].replace('\r', '').replace('\n', ''))
            print("用例名称:%s"%row["name"].replace('\r', '').replace('\n', ''))
            print("前提条件:%s"%row["precondition"].replace('\r', '').replace('\n', ''))
            print("用例步骤:%s"%row["action"].replace('\r', '').replace('\n', ''))
            print("期望结果:%s"%row["expect_result"].replace('\r', '').replace('\n', ''))
            print("用例脚本:\n%s"%row[conf_dict['CaseProjectColumn']])
            lock_print=False
            print("请输入[e:退出,c:继续，10秒后默认继续]:")
            i, o, e = select.select( [sys.stdin], [], [], 10 )
            if (i):
                cmd=sys.stdin.readline().strip()
            if "e" in cmd:
                is_exit=True
            lock_print=True
            interpriter(client,db,cmd_list)
    is_exit=True

def can_send_thread(can_bus):
    global msg_send_dict
    global lock_print
    global is_exit
    tm=time.time()*1000
    while not is_exit:
        lk.acquire()
        tm=time.time()*1000
        for id in msg_send_dict:
            if tm - msg_send_dict[id].timestamp > msg_send_dict[id].cycle_time:
                # data = msg_send_dict[id].encode(msg_send_dict[id].signals_value,scaling=True, padding=False, strict=True)
                data = msg_send_dict[id].encode(msg_send_dict[id].signals_value,scaling=True, padding=False, strict=False)
                can_msg = can.Message(arbitration_id=id, data=data,is_extended_id=False)
                try:
                    can_bus.send(can_msg)
                except Exception as e:
                    if 'y' in conf_dict['PrintInfo'].lower():
                        print("周立功CAN卡连接异常",e)
                msg_send_dict[id].timestamp=tm
                # if lock_print:print("Message sent on {}".format(bus.channel_info))
                if 'y' in conf_dict['PrintInfo'].lower():
                    print(can_msg)
                    # print("TX:%s DATA:%s"%(hex(id), " ".join(["{:02x}".format(x) for x in data])))
        lk.release()
        time.sleep(0.01)

def can_recv_thread(can_bus):
    global is_exit
    global bus_msg_recv_dict
    try:
        for bus_msg in can_bus:
            if is_exit:break
            bus_msg_recv_dict[bus_msg.arbitration_id] = bus_msg
            if 'y' in conf_dict['PrintInfo'].lower(): print(bus_msg)
            # print(msg.arbitration_id, msg.data)
    except Exception as e:
        print("错误：",e)

def handler(signum, frame):
    global is_exit
    is_exit = True
    print("receive a signal %d, is_exit = %d"%(signum, is_exit))

if __name__ == '__main__':
    signal.signal(signal.SIGINT, handler)
    signal.signal(signal.SIGTERM, handler)
    try:
        opts, args = getopt.getopt(sys.argv[1:],"hr:c:n:f:s:d:t:p:i:",["help","PrintInfo","RS232SerialPort=","CANChanel=","DeviceSerialNo=","DBCFile=","CaseScript=","CaseDatabase=","CaseTable=","CaseProjectColumn="])
    except getopt.GetoptError:
        print("""cantest.py 
              -r <RS232SerialPort> 
              -c <CANChanel> 
              -n <DeviceSerialNo> 
              -f <DBCFile> 
              -s <CaseScript> 
              -d <CaseDatabase> 
              -t <CaseTable> 
              -p <CaseProjectColumn>
              -i <PrintInfo>""")
        sys.exit(2)
    for opt, arg in opts:
        if opt == '-h':
            print("""cantest.py 
                  -r <RS232SerialPort> 
                  -c <CANChanel> 
                  -n <DeviceSerialNo> 
                  -f <DBCFile> 
                  -s <CaseScript> 
                  -d <CaseDatabase> 
                  -t <CaseTable> 
                  -p <CaseProjectColumn>
                  -i <PrintInfo>""")
            sys.exit()
        elif opt in ("-i", "--PrintInfo"):
            conf_dict['PrintInfo'] = 'y'
        elif opt in ("-r", "--RS232SerialPort"):
            conf_dict['RS232SerialPort'] = arg
        elif opt in ("-c", "--CANChanel"):
            conf_dict['CANChanel'] = arg
        elif opt in ("-n", "--DeviceSerialNo"):
            conf_dict['DeviceSerialNo'] = arg
        elif opt in ("-f", "--DBCFile"):
            conf_dict['DBCFile'] = arg
        elif opt in ("-s", "--CaseScript"):
            conf_dict['CaseScript'] = arg
        elif opt in ("-d", "--CaseDatabase"):
            conf_dict['CaseDatabase'] = arg
        elif opt in ("-t", "--CaseTable"):
            conf_dict['CaseTable'] = arg
        elif opt in ("-p", "--CaseProjectColumn"):
            conf_dict['CaseProjectColumn'] = arg
 
    port_list = list(serial.tools.list_ports.comports())
    port_name_list=[]
    for port in port_list:
        port_name_list.append(port.name)
    if not len(conf_dict['RS232SerialPort']  ):conf_dict['RS232SerialPort']   = input("请输入RS232控制继电器模块串口号(可用:%s):" % port_name_list) 
    if not len(conf_dict['CANChanel']        ):conf_dict['CANChanel']         = input("请输入USBCAN-II通道号[默认：0]：")                          or '0'
    # if not len(conf_dict['DeviceSerialNo']   ):conf_dict['DeviceSerialNo']    = input("请输入车机adb设备序列号[默认：]：")                         or ''
    if not len(conf_dict['DBCFile']          ):conf_dict['DBCFile']           = input("请输入项目CAN总线数据库(.dbc)路径[默认:dbc/example.dbc]：") or 'dbc/example.dbc'
    if not len(conf_dict['CaseScript']       ):conf_dict['CaseScript']        = input("请输入梧桐自定义脚本文件路径[默认：case_script.asc]：")     or 'case_script.asc'
    if not len(conf_dict['host']             ):conf_dict['host']              = input("请输入测试用例数据库名字[默认：10.1.21.240]：")             or '10.1.21.240'
    if not len(conf_dict['port']             ):conf_dict['port']              = input("请输入测试用例数据表名字[默认：8989]：")                    or '8989'
    if not len(conf_dict['user']             ):conf_dict['user']              = input("请输入测试用例数据表中项目名字[默认：root]：")              or 'root'
    if not len(conf_dict['password']         ):conf_dict['password']          = input("请输入测试用例数据表中项目名字[默认：root]：")              or 'root'
    if not len(conf_dict['CaseDatabase']     ):conf_dict['CaseDatabase']      = input("请输入测试用例数据库名字[默认：can_test]：")                 or 'can_test'
    if not len(conf_dict['CaseTable']        ):conf_dict['CaseTable']         = input("请输入测试用例数据表名字[默认：testcase]：")                or 'testcase'
    if not len(conf_dict['CaseProjectColumn']):conf_dict['CaseProjectColumn'] = input("请输入测试用例数据表中项目名字[默认：S302ICA]：")           or 'S302ICA'
    if not len(conf_dict['PrintInfo']):conf_dict['PrintInfo'] = input("请输入是否打印调试信息[默认：n/N]：")           or 'n'
    # Default is "127.0.0.1" and 5037
    can_bus = can.interface.Bus(bustype='canalystii', channel=0, baud=500000)
    thread_list = []    
    client = AdbClient(host="127.0.0.1", port=5037)
    t1=threading.Thread(target=read_thread,args=(client ,))
    thread_list.append(t1)
    t2=threading.Thread(target=can_send_thread,args=(can_bus ,))
    thread_list.append(t2)
    t3=threading.Thread(target=can_recv_thread,args=(can_bus ,))
    thread_list.append(t3)
    for t in thread_list:
        t.setDaemon(True)  # 设置为守护线程，不会因主线程结束而中断
        t.start()
    # for t in thread_list:
    #     t.join()  # 子线程全部加入，主线程等所有子线程运行完毕
    while 1:
        alive = False
        for t in thread_list:
            alive = alive or t.isAlive()
        if not alive:
            break
    try:
        print("关闭CAN总线")
        can_bus.shutdown()
    except Exception as e:
        print("关闭CAN总线错误：",e)

    input("测试完成,任意键退出...")

